#
# Playlist.py
#
# This work is released under the GNU GPL, version 2 or later.
#
from Utils.kdeemul import *
from Utils.PListParser import *
from CD.Burner import *
from Player import *
from UI.BurnerSettingsDialog import *
from Utils.utils import *
from CD.CDROM import *
from Utils.Settings import *
import os,Utils.crushToAscii,random


class Playlist(object):
	def __init__(self,library = None):
		self.initStuff(library)
	
	def initStuff(self,library = None):
		self._qObject = QObject()
		self._library = library
		self._trackItems = []
		self.master = False
		self.trash = False
		self.purchased = False
		self.smart = False
		self.shuffledList = []
		self.currentTrackList = []
		if library:
			self.name = library.uniquePlaylistName()
			self.playlistID = library.uniquePlaylistID()
		else:
			self.name = ""
			self.playlistID = 4242
		self.shared = False

	def __getstate__(self):
		return {'Library':self._library,'Properties':self.getPList()}
	
	def __setstate__(self,stuff):
		self.initStuff()
		self._library = stuff['Library']
		self.setPList(stuff['Properties'])
		self._qObject = QObject()

	def qObject(self):
		try: return self._qObject
		except:
			self._qObject = QObject()
			return self._qObject
	
	def library(self):
		return self._library

	def emit(self,signal,args):
		self.qObject().emit(signal,args)

	def connect(self,signal,target):
		QObject.connect(self.qObject(),signal,target)

	def markDirty(self):
		try: self._library.markDirty()
		except: pass

	def getName(self):
		return self._name
	def setName(self,name):
		try:    oldName = self._name
		except: oldName = ""
		self._name = name
		self.markDirty()
		self.emit(PYSIGNAL('renamed'),(self,oldName,name))

	name = property(getName,setName)
	
	def hasTracks(self):
		return len(self._trackItems)>0

	def firstTrack(self):
		try: return self.trackWithTrackID(self._trackItems[0]['Track ID'])
		except: return None

	def addTrack(self,track):
		self.addTrackID(track.trackID)
		self.shuffleTracks()

	def addTracks(self,tracks):
		self.addTrackIDs(map(lambda track:track.trackID,tracks))
		self.shuffleTracks()

	def addTrackID(self,trackID):
		self._trackItems.append({'Track ID':trackID})
		self.currentTrackList.append(trackID)
		self.emit(PYSIGNAL('addedTrack'),(self,trackID))
		self.markDirty()

	def addTrackIDs(self,trackIDs):
		for trackID in trackIDs:
			if not self.containsTrackID(trackID):
				self.addTrackID(trackID)
		self.shuffledList = []

	def removeTrack(self,track):
		self.removeTrackID(track.trackID)
		self.shuffledList = []

	def removeTrackID(self,trackID):
		self.emit(PYSIGNAL('aboutToRemoveTrack'),(self,trackID))
		for trackItem in self._trackItems:
			if trackItem['Track ID']==trackID:
				try:
					self._trackItems.remove(trackItem)
					self.currentTrackList.remove(trackID)
				except: pass
				break
		self.markDirty()
		self.emit(PYSIGNAL('removedTrack'),(self,trackID))
	
	def removeTrackIDs(self,trackIDs):
		for trackID in trackIDs:
			self.removeTrackID(trackID)
		self.shuffledList = []
		self.emit(PYSIGNAL('removedTracks'),(self,trackIDs))

	def totalDuration(self):
		duration  = 0
		for track in self.getTracks():
			try: duration = duration+track.totalTime # may be some tracks without duration!
			except: pass
		return duration

	def trackCount(self):
		return len(self._trackItems)
	
	def info(self):
		return {'Track Count':self.trackCount(),'Duration':self.totalDuration()}
			
	def containsTrack(self,track):
		return self.containsTrackID(track.trackID)

	def containsTrackID(self,trackID):
		for track in self._trackItems:
			if track['Track ID']==trackID:
				return True
		return False

	def trackAfter(self,track):
		trackIDs = self.getTrackIDs()
		try:
			index = trackIDs.index(track.trackID)
			index = index+1
			if index>=len(trackIDs):
				index = 0
			return self.trackWithTrackID(trackIDs[index])
		except:
			return None
	
	def trackBefore(self,track):
		trackIDs = self.getTrackIDs()
		try:
			index = trackIDs.index(track.trackID)
			index = index-1
			if index<0:
				index = len(trackIDs)-1
			return self.trackWithTrackID(trackIDs[index])
		except:
			return None

	def getTrackIDs(self):
		return map(lambda item: item['Track ID'],self._trackItems)
	trackIDs = property(getTrackIDs)

	def getTracks(self):
		return map(lambda trackID: self.trackWithTrackID(trackID),self.trackIDs)
	tracks = property(getTracks)

	def getEnabledTracks(self):
		return filter(lambda track: track.enabled,self.getTracks())

	def trackWithTrackID(self,trackID):
		return self._library.trackWithTrackID(trackID)
	
	def trackWithLocation(self,location):
		tracks = self.getTracks()
		for track in tracks:
			if track.originalLocation==location:
				return track
		return None

	#
	# make sure the playlist contains trackIDs that correspond to real tracks, remove the failures
	#
	def validate(self):
		for trackID in self.trackIDs:
			track = self.trackWithTrackID(trackID)
			if track==None: # invalid track
				self.removeTrackID(trackID)
				
	def shuffleTracks(self):
		trackIDs = self.currentTrackList #self.getTrackIDs()
		self.shuffledList = trackIDs[:]
		for i in xrange(len(self.shuffledList)-1,0,-1):
			j = int(random.random()*(i+1))
			self.shuffledList[i],self.shuffledList[j] = self.shuffledList[j],self.shuffledList[i]
	
	def getNextTrack(self,track):
		return self.getSubsequentTrack(track,False)
	
	def getPrevTrack(self,track):
		return self.getSubsequentTrack(track,True)
	
	def getSubsequentTrack(self,track,reverse):
		from Player import Player
		player = Player.singleton()
		if player.repeat=='one' and track and track.enabled:
			return track
		if player.shuffle:
			if len(self.shuffledList)<=0:
				self.shuffleTracks()
			trackIDs = self.shuffledList
		else:
			trackIDs = self.currentTrackList
		tracks = map(lambda trackID:self.trackWithTrackID(trackID),trackIDs)
		tracks = filter(lambda aTrack:aTrack.enabled or aTrack==track,tracks)
		numTracks = len(tracks)
		if numTracks==0: return None
		if (reverse): tracks.reverse()
		try:
			trackIndex = tracks.index(track)
			nextIndex = (trackIndex+1) % numTracks
			if not player.shuffle and player.repeat == 'off' and nextIndex<=trackIndex: return None
			track = tracks[nextIndex]
			if track.enabled: return track
			return None
		except:
			return None

	def updateCurrentTracks(self,tracks):
		#print "updating current track to",tracks
		self.currentTrackList = map(lambda track:track.trackID,tracks)
##~ 		self.currentTrackList = []
##~ 		for e in tracks: self.currentTrackList.append(e.trackID)
		self.shuffledList = []
				
	def isMaster(self):
		return self.master
	
	def isTrash(self):
		return self.trash

	def isPurchased(self):
		return self.purchased
	
	def canAcceptTrackDrags(self):
		return True
	
	def canAcceptFileDrags(self):
		return True

	def canInitiateDrag(self):
		return True

	def burn(self,burner,speed):
		tracks = self.getEnabledTracks()
		if len(tracks)>0:
			Burner.singleton().burnTracks(tracks,burner,speed)

	def exportPList(self):
		pList = {'Major Version':1,'Minor Version':0,'Application Version':'1.0'}
		playlists = []
		playlists.append(self.pList)
		pList['Playlists'] = playlists
		tracks = {}
		for item in self._trackItems:
			trackID = item['Track ID']
			track = self.trackWithTrackID(trackID)
			if track:
				tracks[str(trackID)] = track.pList
		pList['Tracks'] = tracks
		return [pList]

	def exportSongList(self):
		try: fileName = KFileDialog.getSaveFileName("","*.xml",None,"export playlist",i18n("Export Song List"))
		except: fileName = KFileDialog.getSaveFileName("","*.xml", None, i18n('Export Song List'))
		if fileName:
			self.exportTo(fileName)

	def exportTo(self,fileName):
		writer = PListWriter()
		writer.unparse(self.exportPList(),fileName)
	
	def indexOfTrackID(self,trackID):
		index = 0
		for item in self._trackItems:
			if item['Track ID']==trackID:
				return index
			index = index+1
		return -1
	
	def shuffle(self,trackIDs,insertPoint):
		for trackID in trackIDs:
			index = self.indexOfTrackID(trackID)
			if index<insertPoint:
				insertPoint = insertPoint-1
		#insertPoint -= len(trackIDs)
		self.removeTrackIDs(trackIDs)
		self.insertTrackIDsBefore(trackIDs,insertPoint)
		self.emit(PYSIGNAL("shuffled"),(self,None))

	def insertTrackIDsBefore(self,trackIDs,insertPoint):
		for trackID in trackIDs:
			self.insertTrackIDBefore(trackID,insertPoint)
			insertPoint = insertPoint+1
	
	def insertTrackIDBefore(self,trackID,insertPoint):
		self._trackItems.insert(insertPoint,{'Track ID':trackID})

	def canBurn(self):
		burnType = Settings.settings().get('Burn Disc Type','audio')
		if burnType=='mp3' or burnType=='data':
			crushed = {}
			for track in self.getEnabledTracks():
				(path,fileName) = os.path.split(track.location)
				crushedName = crushToAscii.crush(fileName)
				if crushed.has_key(crushedName):
					return False
				crushed[crushedName] = 1
		return True

	def burnPlaylistToDisc(self):
		# XXX DSM uncomment for testing
		#self.burn("0,0,0",0)
		#return
		if CDROMS.singleton().canBurn()>0:
			if self.canBurn():
				dialog = LBurnerSettingsDialog()
				ret = dialog.exec_loop()
				if ret==QDialog.Accepted:
					burner = dialog.getCurrentBurner()
					if burner:
						speed = dialog.getCurrentSpeed()
						#print "burning with",burner,"speed",speed
						self.burn(burner,speed)
			else:
				KMessageBox.error(None,i18n("Cannot burn CD due to file name conflicts"),i18n("Cannot Burn"))
		else:
			KMessageBox.error(None,i18n("CD Burner not available"),i18n("Cannot Burn"))

	def trackIDsForArtists(self,artists):
##~ 		tracks = self.tracks
##~ 		trackIDs = []
##~ 		for track in tracks:
##~ 			if track and (unikode(track.artist) in artists):
##~ 				trackIDs.append(track.trackID)
##~ 		return trackIDs
		return self.trackIDsForArtistsAndAlbums(artists,None)

	def trackIDsForAlbums(self,albums):
		return self.trackIDsForArtistsAndAlbums(None,albums)
	
	def trackIDsForArtistsAndAlbums(self,artists,albums):
		trackIDs = []
		for track in self.tracks:
			artist = None
			if track.artist: artist = unikode(track.artist)
			album = None
			if track.album: album = unikode(track.album)			
			if artists==None or len(artists)==0 or (artist and artist in artists):
				if albums==None or len(albums)==0 or (album and album in albums):
					trackIDs.append(track.trackID)
		return trackIDs

	def moveTracksToTrash(self):
		trash = self.library().playlistWithName("Trash")
		print "moving",self.trackIDs,"to",trash
		trash.addTrackIDs(self.trackIDs)
		#print "removing tracks from all playlists"
		self.library().removeTrackIDsFromAllPlaylists(self.trackIDs)

	def getPList(self):
		playlist = {'Playlist ID':self.playlistID,'Name':self.name}
		items = map(lambda item: {'Track ID':item['Track ID']},self._trackItems)
##~ 		items = []
##~ 		for item in self._trackItems:
##~ 			items.append({'Track ID':item['Track ID']})
		playlist['Playlist Items'] = items
		if self.master:
			playlist['Master'] = True
		if self.trash:
			playlist['Trash'] = True
		if self.purchased:
			playlist['Purchased Music'] = True
		if self.smart:
			playlist['Smart'] = True
		if self.shared:
			playlist['Shared'] = True
		return playlist
	def setPList(self,pList):
		self.name = unikode(pList.get('Name','Unknown'))
		self.playlistID = pList.get('Playlist ID',0)
		if pList.has_key('Playlist Items'):
			a = pList['Playlist Items']
			for item in a:
				self.addTrackID(item['Track ID'])
		self.master = pList.get('Master',False)
		self.trash = pList.get('Trash',False)
		self.purchased = pList.get('Purchased Music',False)
		self.smart = pList.get('Smart',False)
		self.shared = pList.get('Shared',False)
		#self.validate()
	pList = property(getPList,setPList)

	def getRemotePList(self,withTracks = False):
		playlist = {'Playlist ID':self.playlistID,'Name':self.name,'Master':self.master}
		playlist.update(self.info())
		if withTracks:
			items = map(lambda item: self.trackWithTrackID(item['Track ID']).remotePList,self._trackItems)
			playlist['Playlist Items'] = items
		return playlist
	remotePList = property(getRemotePList)

	def staticFromPList(pList):
		t = Playlist()
		t.pList = pList
		return t

	fromPList = staticmethod(staticFromPList)

if __name__=='__main__':
	p = Playlist()
	p.addTrackID(3)
	print p.trackIDs
	print p.pList
